/* Gauge - Performance Measurement Framework for Mobile Platforms
 * Copyright (C) 2013, Juha Hytonen <juha.a.hytonen@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.gauge.GaugeServer;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.gauge.AbstractFileMeasurementStorage;
import org.gauge.Measurement;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.Period;

/**
 * Stores measurements into a CSV (comma separated values) file.
 */
public class CSVFileMeasurementStorage extends AbstractFileMeasurementStorage {
	
	private static final String Separator = ",";
	private static final int FirstValuePos = 2;
	
	private SortedMap<DateTime, Measurement> measurements;
	private List<String> metrics;
	private Map<String, String> metricCaptions;
	
	public CSVFileMeasurementStorage(File file) throws Exception {
		super(file);
		measurements = new TreeMap<DateTime, Measurement>();
	}

	@Override
	protected void addMeasurement(Measurement measurement) throws Exception {
		measurements.put(measurement.GetDateTime(), measurement);
	}
	
	@Override
	protected void close() throws Exception {
		saveMetrics();
		super.close();
	}
	
	private void saveMetrics() throws Exception {
		extractMetricsFromMeasurements();
		writeCaptionLine();
		
		for (Map.Entry<DateTime, Measurement> entry : measurements.entrySet()) {
			writeValueLine(entry.getValue());
		}
	}
	
	private void extractMetricsFromMeasurements() {
		metrics = new ArrayList<String>();
		metricCaptions = new HashMap<String, String>();
		for (Measurement measurement : measurements.values()) {
			String metric = measurement.GetName();
			String measurementUnit = measurement.GetMeasurementUnit();
			String metricCaption = createMetricCaptionFrom(metric, measurementUnit);
			if (!metrics.contains(metric)) {
				metrics.add(metric);
				metricCaptions.put(metric, metricCaption);
			}
		}
	}
	
	private String createMetricCaptionFrom(String metric, String measurementUnit) {
		return new String(metric + " (" + measurementUnit + ")");
	}
	
	private void writeCaptionLine() throws Exception {
		StringBuilder line = new StringBuilder();
		line.append(Separator);
		for(String metric : metrics) {
			line.append(Separator);
			line.append(getMetricCaptionFor(metric));
		}
		writeLine(line.toString());
	}
	
	private String getMetricCaptionFor(String metric) {
		return metricCaptions.get(metric);
	}
	
	private void writeValueLine(Measurement measurement) throws Exception {
		int position = getPositionFor(measurement.GetName());
		String timestamp = String.valueOf(measurement.GetDateTime().getMillis());
		String value = String.valueOf(measurement.GetValue());
		
		StringBuilder line = new StringBuilder();
		appendTimestampsTo(line, timestamp);
		appendValueTo(line, value, position);
		writeLine(line.toString());
	}
	
	private int getPositionFor(String metric) {
		return (metrics.indexOf(metric) + FirstValuePos + 1);
	}
	
	private void appendTimestampsTo(StringBuilder line, String timestamp) {
		Period deltaTime = getDeltaTimeFor(timestamp);		
		line.append(timestamp);
		line.append(Separator);
		line.append(formatDeltaTime(deltaTime));
	}
	
	private Period getDeltaTimeFor(String timestamp) {
		DateTime zeroTime = getZeroTime();
		DateTime currentTime = new DateTime(Long.valueOf(timestamp));
		Interval interval = new Interval(zeroTime, currentTime);
		return interval.toPeriod();
	}
	
	private DateTime getZeroTime() {
		Iterator<DateTime> iter = measurements.keySet().iterator();
		return iter.next();
	}
	
	private String formatDeltaTime(Period deltaTime) {
		StringBuilder deltaTimeBuilder = new StringBuilder();
		deltaTimeBuilder.append("+");
		deltaTimeBuilder.append(String.valueOf(deltaTime.getHours()));
		deltaTimeBuilder.append(":");
		deltaTimeBuilder.append(String.valueOf(deltaTime.getMinutes()));
		deltaTimeBuilder.append(":");
		deltaTimeBuilder.append(String.valueOf(deltaTime.getSeconds()));
		deltaTimeBuilder.append(".");
		deltaTimeBuilder.append(String.valueOf(deltaTime.getMillis()));
		return deltaTimeBuilder.toString();
	}
	
	private void appendValueTo(StringBuilder line, String value, int position) {
		appendEmptyValuesTo(line, position - FirstValuePos);
		line.append(value);
		appendEmptyValuesTo(line, getMetricCount() - position);
	}
	
	private void appendEmptyValuesTo(StringBuilder line, int count) {
		for(int i = 0; i < count; i++) {
			line.append(Separator);
		}
	}
	
	private int getMetricCount() {
		return (metrics.size() + FirstValuePos);
	}

}
